Skip to content

Memory lru #695

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 34 commits into from
Closed

Memory lru #695

wants to merge 34 commits into from

Conversation

gsoltis
Copy link

@gsoltis gsoltis commented Jan 23, 2018

Do Not Merge.

This PR shows an implementation of FSTLRUGarbageCollector. The functioning of that GC strategy requires a few changes to FSTQueryCache.h, which are shown here and implemented for FSTMemoryQueryCache.

Tests are provided for various steps in the process of running the LRU GC, however the GC is not wired into the Local Store and will not yet be triggered. I expect that the tests will run against LevelDB persistence which minimal modifications once I have implemented the necessary pieces in FSTLevelDBQueryCache and any other necessary places.

There are two notable changes beyond the LRU implementation and associated query cache changes:

  • The local store informs the QueryCache of documents that have been written at the time when they are added to the RemoteDocumentCache.
  • FSTMemoryQueryCache now tracks documents that have been written from the point when they are ack'd until the point where they are added to a query target. The second part is an optimization, we could theoretically keep them even after they appear in a target.

Not yet implemented:

  • LevelDB anything. Haven't touched it yet
  • Refreshing sequence numbers for queries after receiving resume tokens.
  • Handling documents removed from a query in local store

* limitations under the License.
*/

#import <XCTest/XCTest.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: separate framework imports from the rest

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

- (void)testPickSequenceNumberPercentile {
const int numTestCases = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note you can define a struct locally here within this function and initialize it just as you've done. Something like this would allow you to avoid the fiddly indexes in the code below.

struct Case {
  int queries;
  int expected;
};
Case testCases[numTestCases] = {{0, 0}, {10, 1}, {9, 0}, {50, 5}, {49, 4}};

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I didn't know you could just define structs wherever. I definitely like that better.

@@ -0,0 +1,101 @@
#import "Firestore/Source/Local/FSTLRUGarbageCollector.h"
#import <queue>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline before

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

class RollingSequenceNumberBuffer {
public:
RollingSequenceNumberBuffer(NSUInteger max_elements) : max_elements_(max_elements) {
queue_ = std::make_unique<priority_queue<FSTListenSequenceNumber> >();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're not letting this priority queue escape or otherwise be shared you don't need to heap allocate it.

Just make the member

std::priority_queue<FSTListenceSequenceNumber> queue_;

Then you can initialize it exactly as you have max_elements_.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. In the future, I think I can even stack allocate the backing vector, since I know the max size at constructor time.


const FSTListenSequenceNumber kFSTListenSequenceNumberInvalid = -1;

using std::priority_queue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of meh on using statements for members of std. If you're going to use them, use them consistently.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, removed the using.

queue_ = std::make_unique<priority_queue<FSTListenSequenceNumber> >();
}

RollingSequenceNumberBuffer(const RollingSequenceNumberBuffer &other) = delete;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only need to define one copy constructor and assignment operator as deleted to suppress the other.

You can prove it to yourself with a snippet like the following (though please delete after):

static_assert(!std::is_move_constructable<RollingSequenceNumberBuffer>::type, "no moves");
static_assert(!std::is_move_assignable<RollingSequenceNumberBuffer>::type, "no move assignments");
static_assert(!std::is_copy_constructable<RollingSequenceNumberBuffer>::type, "no copies");
static_assert(!std::is_copy_assignable<RollingSequenceNumberBuffer>::type, "no copy assignments");

https://stackoverflow.com/questions/33776697/deleting-copy-constructors-and-copy-assignment-operators-which-of-them-are-esse

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the recommended fashion, I was just copying what I saw elsewhere. Any difference / preference for which one of each is kept?

For reference: https://github.com/firebase/firebase-ios-sdk/blob/master/Firestore/core/src/firebase/firestore/remote/datastore.h

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do what I say, not what I do :-P.

@@ -418,6 +421,8 @@ - (void)releaseQuery:(FSTQuery *)query {
FSTAssert(queryData, @"Tried to release nonexistent query: %@", query);

[self.localViewReferences removeReferencesForID:queryData.targetID];
// TODO(gsoltis): kill this if statement, give GC a reference to query cache,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definitely sounds right.

@@ -0,0 +1,29 @@
#import <Foundation/Foundation.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing copyright (and that's why Travis failed your build).


@end

@implementation FSTLRUGarbageCollector {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty block here is unnecessary.


const FSTListenSequenceNumber kFSTListenSequenceNumberInvalid = -1;

class RollingSequenceNumberBuffer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on the purpose of the class.

In particular it's helpful to understand why a priority queue that efficiently tracks the highest seen sequence number is helpful. I think it's because you're looking for the oldest (i.e. smallest) max_elements number of sequence numbers, right?

: max_elements_(max_elements), queue_(std::priority_queue<FSTListenSequenceNumber>()) {
}

RollingSequenceNumberBuffer(const RollingSequenceNumberBuffer &other) = delete;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you declare any copy constructor/assignment that implicitly precludes the move operations from being generated. The deletions of the moves are redundant.

https://stackoverflow.com/questions/33096653/make-a-class-non-copyable-and-non-movable

@@ -0,0 +1,100 @@
#import "Firestore/Source/Local/FSTLRUGarbageCollector.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright 2018

}
}
}];
[self.queries removeObjectsForKeys:toRemove];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need to remove document keys from the mutated document sequence numbers?

@@ -98,6 +98,18 @@ NS_ASSUME_NONNULL_BEGIN
/** Removes the cached entry for the given query data (no-op if no entry exists). */
- (void)removeQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group;

- (void)enumerateSequenceNumbersUsingBlock:(void (^)(FSTListenSequenceNumber sequenceNumber,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should note that it's not obligated to return sequence numbers in order.

- (void)enumerateSequenceNumbersUsingBlock:(void (^)(FSTListenSequenceNumber sequenceNumber,
BOOL *stop))block;

- (NSUInteger)removeQueriesThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments. Removes queries with sequence numbers up to and including the given sequence number.

What does live queries have to do with it? Are they excluded? Does encountering a live query terminate the removal?

NSMutableArray<FSTQuery *> *toRemove = [NSMutableArray array];
[self.queries
enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryData *queryData, BOOL *stop) {
if (queryData.sequenceNumber <= sequenceNumber) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be a compound condition separated by &&?

group:(__unused FSTWriteGroup *)group {
NSMutableArray<FSTQuery *> *toRemove = [NSMutableArray array];
[self.queries
enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryData *queryData, BOOL *stop) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can avoid this second scan if your priority were to track (sequence number, target id) pairs. Order by the first (which yields the same sequence numbers) but then once you're done accumulating the queue you have all the primary keys of the targets to delete in hand.

You'll need to store something else to indicate it was a mutated document key instead; not sure if this is required (see other questions).

@gsoltis
Copy link
Author

gsoltis commented Feb 20, 2018

Sorry, this wasn't intended to be reviewed yet. I'll assign someone when it is. I'd like to get a review of another PR first before tackling this one. I will hopefully have that one up today or tomorrow.

@mikelehen mikelehen removed their request for review April 11, 2018 23:25
@ryanwilson
Copy link
Member

@gsoltis is this PR abandoned? If so, we should close it.

@gsoltis
Copy link
Author

gsoltis commented Dec 13, 2018

Yup, a different PR was merged that included this. Closing.

@gsoltis gsoltis closed this Dec 13, 2018
@gsoltis gsoltis deleted the memory_lru branch December 13, 2018 19:16
@firebase firebase locked and limited conversation to collaborators Nov 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants